Análisis de https://www.nature.com/articles/srep00196.pdf

Podemos usar read_lines_chunked si el archivo original es grande. En este ejemplo, filtramos las recetas East Asian:

library(tidyverse)

limpiar <- function(lineas,...){
  str_split(lineas, ',') %>% 
    keep(~.x[1] == 'EastAsian') %>%
    map(~.x[-1]) %>% # quitar tipo de cocina
    map(~.x[nchar(.x) > 0]) # quitar elementos vacios
}
callback_limpiar <- ListCallback$new(limpiar)
filtrado <- read_lines_chunked('../datos/recetas/srep00196-s3.csv',
                    skip = 1, callback = callback_limpiar, chunk_size = 1000)
recetas <-  filtrado %>% flatten
recetas[1:10]
[[1]]
[1] "beef_broth" "egg"        "soy_sauce"  "soybean"   

[[2]]
[1] "sesame_oil"          "beef"                "roasted_sesame_seed" "matsutake"          
[5] "black_pepper"        "scallion"            "garlic"              "soy_sauce"          

[[3]]
[1] "vinegar"             "roasted_sesame_seed" "cayenne"             "scallion"           
[5] "garlic"              "soybean"             "cucumber"            "rice"               

[[4]]
 [1] "beef"                "roasted_sesame_seed" "soy_sauce"           "cayenne"            
 [5] "ginger"              "scallion"            "lettuce"             "garlic"             
 [9] "vegetable"           "sake"               

[[5]]
[1] "garlic"    "fish"      "cayenne"   "soy_sauce" "potato"   

[[6]]
 [1] "sweet_potato"        "onion"               "roasted_sesame_seed" "soy_sauce"          
 [5] "cayenne"             "ginger"              "soybean"             "vegetable"          
 [9] "cabbage"             "rice"                "chicken"             "sesame_oil"         

[[7]]
 [1] "sesame_oil"          "radish"              "fish"                "black_pepper"       
 [5] "ginger"              "garlic"              "seaweed"             "shrimp"             
 [9] "beef"                "roasted_sesame_seed" "soy_sauce"           "cayenne"            
[13] "chinese_cabbage"     "scallion"            "sesame_seed"         "rice"               

[[8]]
 [1] "vinegar"   "radish"    "fish"      "cayenne"   "scallion"  "cucumber"  "soybean"  
 [8] "vegetable" "garlic"    "rice"      "soy_sauce"

[[9]]
[1] "radish"        "fish"          "cayenne"       "ginger"        "scallion"     
[6] "garlic"        "vegetable_oil" "soy_sauce"    

[[10]]
[1] "nut"         "cucumber"    "sesame_seed" "soybean"    
library(arules)
length(recetas)
[1] 2512
## No hacer mucho más chico que este soporte, pues tenemos relativamente
## pocas transacciones:
pars <- list(support = 0.05,  target = 'frequent itemsets',
             ext = TRUE)
ap_recetas <- apriori(recetas, parameter = pars)
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 125 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[242 item(s), 2512 transaction(s)] done [0.00s].
sorting and recoding items ... [41 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 6 done [0.00s].
sorting transactions ... done [0.00s].
writing ... [628 set(s)] done [0.00s].
creating S4 object  ... done [0.00s].
length(ap_recetas)
[1] 628

Vemos los items frecuentes

frecs <- ap_recetas %>% subset(size(.) == 1 ) %>% sort(by = 'support') %>%
 DATAFRAME
DT::datatable(frecs %>% mutate_if(is.numeric, function(x) round(x, 3)))
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Y ahora examinamos combinaciones frecuentes de distintos tamaños

ap_recetas %>% 
  subset(size(.) == 2) %>%
  subset(support > 0.20) %>%
  sort(by = 'support') %>%
  inspect

Incluso hay algunas combinaciones de 4 ingredientes que ocurren con frecuencia alta: estos ingredientes son bases de salsas, combinaciones de condimentos:

ap_recetas %>% 
  subset(size(.) == 4) %>%
  subset(support > 0.10) %>%
  sort(by = 'support') %>%
  inspect

Extracción de reglas

pars <- list(support = 0.01, confidence = 0.10,
             target = 'rules',
             ext = TRUE)
reglas_recetas <- apriori(recetas, parameter = pars)
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 25 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[242 item(s), 2512 transaction(s)] done [0.00s].
sorting and recoding items ... [88 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 6 7 8 done [0.02s].
writing ... [50181 rule(s)] done [0.01s].
creating S4 object  ... done [0.01s].
agregar_hyperlift <- function(reglas, trans){
  quality(reglas) <- cbind(quality(reglas), 
    hyper_lift = interestMeasure(reglas, measure = "hyperLift", 
    transactions = trans))
  reglas
}
reglas_recetas <- agregar_hyperlift(reglas_recetas, recetas)

Análisis de pares comunes

library(arulesViz)
Loading required package: grid
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
reglas_1 <- subset(reglas_recetas, hyper_lift > 1.1 & support > 0.1 & confidence > 0.40)
length(reglas_1)
[1] 213
reglas_tam_2 <- subset(reglas_1, size(reglas_1)==2)
#inspect(reglas_tam_2 %>% sort(by = 'hyper_lift')) 
plot(reglas_1 %>% subset(support > 0.2), engine = "plotly")
To reduce overplotting, jitter is added! Use jitter = 0 to prevent jitter.
`arrange_()` is deprecated as of dplyr 0.7.0.
Please use `arrange()` instead.
See vignette('programming') for more help
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
library(tidygraph)

Attaching package: ‘tidygraph’

The following object is masked from ‘package:stats’:

    filter
library(ggraph)
frecs <- 
df_reglas <- reglas_tam_2 %>% DATAFRAME %>% rename(from=LHS, to=RHS) %>% data.frame
df_reglas$weight <- log(df_reglas$lift)
graph_1 <- as_tbl_graph(df_reglas) %>%
  mutate(centrality = centrality_degree(mode = "all")) 
set.seed(881)
ggraph(graph_1, layout = 'fr') +
  geom_edge_link(aes(alpha=lift), 
                 colour = 'red',
                 arrow = arrow(length = unit(4, 'mm'))) + 
  geom_node_point(aes(size = centrality, colour = centrality)) + 
  geom_node_text(aes(label = name), size=4,
                 colour = 'gray20', repel=TRUE) +
  theme_graph(base_family = "sans")

reglas_1 <- subset(reglas_recetas, hyper_lift > 1.5 & confidence > 0.1)
length(reglas_1)
[1] 11068
reglas_tam_2 <- subset(reglas_1, size(reglas_1)==2)
length(reglas_tam_2)
[1] 132
library(tidygraph)
library(ggraph)
df_reglas <- reglas_tam_2 %>% DATAFRAME %>% rename(from=LHS, to=RHS) %>% as_data_frame
`as_data_frame()` is deprecated as of tibble 2.0.0.
Please use `as_tibble()` instead.
The signature and semantics have changed, see `?as_tibble`.
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
df_reglas$weight <- log(df_reglas$hyper_lift)
graph_1 <- as_tbl_graph(df_reglas) %>%
  mutate(centrality = centrality_degree(mode = "all")) 

ggraph(graph_1, layout = 'fr', start.temp=100) +
  geom_edge_link(aes(alpha=lift), 
                 colour = 'red',
                 arrow = arrow(length = unit(4, 'mm'))) + 
  geom_node_point(aes(size = centrality, colour = centrality)) + 
  geom_node_text(aes(label = name), size=4,
                 colour = 'gray20', repel=TRUE) +
  theme_graph(base_family = "sans")

Exportamos para examinar en Gephi:

write_csv(df_reglas %>% rename(source=from, target=to) %>%
            select(-count), 
          path='reglas.csv')
The `path` argument of `write_csv()` is deprecated as of readr 1.4.0.
Please use the `file` argument instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.

Nota

La combinación corn y starch puede deberse en parte a una separación incorrecta en el procesamiento de los datos (corn starch o maizena convertido en dos ingredientes, corn y starch):

df_reglas %>% filter(from == "{corn}", to == "{starch}")

La confianza es considerablemente alta, aunque tenemos pocos datos de esta combinación. Podemos examinar algunos ejemplos:

recetas %>% keep(~ "tomato" %in% .x & "corn" %in% .x) %>% head(10)
[[1]]
 [1] "tomato"        "vinegar"       "pork"          "celery_oil"    "leek"         
 [6] "corn"          "black_pepper"  "pepper"        "ginger"        "pea"          
[11] "garlic"        "soybean"       "soy_sauce"     "chicken_broth" "wine"         

[[2]]
 [1] "tomato"     "vinegar"    "pepper"     "celery_oil" "corn"       "cayenne"   
 [7] "pork"       "garlic"     "soybean"    "vegetable"  "coriander"  "rice"      
[13] "soy_sauce" 

[[3]]
[1] "tomato"     "vinegar"    "pork"       "celery_oil" "soy_sauce"  "ginger"    
[7] "garlic"     "sherry"     "corn"      

[[4]]
 [1] "pepper"        "celery_oil"    "starch"        "corn"          "ginger"       
 [6] "garlic"        "soybean"       "tomato"        "vinegar"       "beef"         
[11] "soy_sauce"     "cayenne"       "scallion"      "bell_pepper"   "vegetable_oil"
[16] "rice"          "wine"         

[[5]]
 [1] "tomato"     "vinegar"    "pork"       "celery_oil" "beef"       "soy_sauce" 
 [7] "ginger"     "garlic"     "corn"       "wine"      

[[6]]
 [1] "tomato"      "vinegar"     "pepper"      "lemon_juice" "celery_oil"  "sake"       
 [7] "corn"        "pork"        "ginger"      "honey"       "garlic"      "soybean"    
[13] "rice"        "soy_sauce"  

[[7]]
[1] "tomato"  "garlic"  "onion"   "bacon"   "corn"    "cayenne" "egg"    

[[8]]
 [1] "pork"              "green_bell_pepper" "celery_oil"        "starch"           
 [5] "corn"              "garlic"            "tomato"            "vinegar"          
 [9] "onion"             "soy_sauce"         "cider"             "scallion"         
[13] "celery"            "pineapple"         "vegetable_oil"     "egg"              

[[9]]
 [1] "tomato"       "vinegar"      "pepper"       "celery_oil"   "roasted_pork"
 [6] "soy_sauce"    "ginger"       "honey"        "garlic"       "cinnamon"    
[11] "soybean"      "sherry"       "corn"         "oyster"      

[[10]]
 [1] "cane_molasses" "tomato"        "pork"          "celery_oil"    "vinegar"      
 [6] "soy_sauce"     "pepper"        "ginger"        "garlic"        "corn"         
LS0tCnRpdGxlOiAiQW7DoWxpc2lzIGRlIGluZ3JlZGllbnRlcyBlbiByZWNldGFzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpBbsOhbGlzaXMgZGUKaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zcmVwMDAxOTYucGRmCgpQb2RlbW9zIHVzYXIgKnJlYWRfbGluZXNfY2h1bmtlZCogc2kgZWwgYXJjaGl2byBvcmlnaW5hbCBlcyBncmFuZGUuIEVuCmVzdGUgZWplbXBsbywgZmlsdHJhbW9zIGxhcyByZWNldGFzICAqRWFzdCBBc2lhbio6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmxpbXBpYXIgPC0gZnVuY3Rpb24obGluZWFzLC4uLil7CiAgc3RyX3NwbGl0KGxpbmVhcywgJywnKSAlPiUgCiAgICBrZWVwKH4ueFsxXSA9PSAnRWFzdEFzaWFuJykgJT4lCiAgICBtYXAofi54Wy0xXSkgJT4lICMgcXVpdGFyIHRpcG8gZGUgY29jaW5hCiAgICBtYXAofi54W25jaGFyKC54KSA+IDBdKSAjIHF1aXRhciBlbGVtZW50b3MgdmFjaW9zCn0KY2FsbGJhY2tfbGltcGlhciA8LSBMaXN0Q2FsbGJhY2skbmV3KGxpbXBpYXIpCmZpbHRyYWRvIDwtIHJlYWRfbGluZXNfY2h1bmtlZCgnLi4vZGF0b3MvcmVjZXRhcy9zcmVwMDAxOTYtczMuY3N2JywKICAgICAgICAgICAgICAgICAgICBza2lwID0gMSwgY2FsbGJhY2sgPSBjYWxsYmFja19saW1waWFyLCBjaHVua19zaXplID0gMTAwMCkKcmVjZXRhcyA8LSAgZmlsdHJhZG8gJT4lIGZsYXR0ZW4KYGBgCgpgYGB7cn0KcmVjZXRhc1sxOjEwXQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShhcnVsZXMpCmxlbmd0aChyZWNldGFzKQojIyBObyBoYWNlciBtdWNobyBtw6FzIGNoaWNvIHF1ZSBlc3RlIHNvcG9ydGUsIHB1ZXMgdGVuZW1vcyByZWxhdGl2YW1lbnRlCiMjIHBvY2FzIHRyYW5zYWNjaW9uZXM6CnBhcnMgPC0gbGlzdChzdXBwb3J0ID0gMC4wNSwgIHRhcmdldCA9ICdmcmVxdWVudCBpdGVtc2V0cycsCiAgICAgICAgICAgICBleHQgPSBUUlVFKQphcF9yZWNldGFzIDwtIGFwcmlvcmkocmVjZXRhcywgcGFyYW1ldGVyID0gcGFycykKbGVuZ3RoKGFwX3JlY2V0YXMpCmBgYAoKVmVtb3MgbG9zIGl0ZW1zIGZyZWN1ZW50ZXMKCmBgYHtyfQpmcmVjcyA8LSBhcF9yZWNldGFzICU+JSBzdWJzZXQoc2l6ZSguKSA9PSAxICkgJT4lIHNvcnQoYnkgPSAnc3VwcG9ydCcpICU+JQogREFUQUZSQU1FCkRUOjpkYXRhdGFibGUoZnJlY3MgJT4lIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSByb3VuZCh4LCAzKSkpCmBgYAoKWSBhaG9yYSBleGFtaW5hbW9zIGNvbWJpbmFjaW9uZXMgZnJlY3VlbnRlcyBkZSBkaXN0aW50b3MgdGFtYcOxb3MKCmBgYHtyfQphcF9yZWNldGFzICU+JSAKICBzdWJzZXQoc2l6ZSguKSA9PSAyKSAlPiUKICBzdWJzZXQoc3VwcG9ydCA+IDAuMjApICU+JQogIHNvcnQoYnkgPSAnc3VwcG9ydCcpICU+JQogIGluc3BlY3QKYGBgCgpJbmNsdXNvIGhheSBhbGd1bmFzIGNvbWJpbmFjaW9uZXMgZGUgNCBpbmdyZWRpZW50ZXMgcXVlIG9jdXJyZW4gY29uIGZyZWN1ZW5jaWEgYWx0YToKZXN0b3MgaW5ncmVkaWVudGVzIHNvbiBiYXNlcyBkZSBzYWxzYXMsIGNvbWJpbmFjaW9uZXMgZGUgY29uZGltZW50b3M6CgpgYGB7cn0KYXBfcmVjZXRhcyAlPiUgCiAgc3Vic2V0KHNpemUoLikgPT0gNCkgJT4lCiAgc3Vic2V0KHN1cHBvcnQgPiAwLjEwKSAlPiUKICBzb3J0KGJ5ID0gJ3N1cHBvcnQnKSAlPiUKICBpbnNwZWN0CmBgYAoKIyMgRXh0cmFjY2nDs24gZGUgcmVnbGFzCgpgYGB7cn0KcGFycyA8LSBsaXN0KHN1cHBvcnQgPSAwLjAxLCBjb25maWRlbmNlID0gMC4xMCwKICAgICAgICAgICAgIHRhcmdldCA9ICdydWxlcycsCiAgICAgICAgICAgICBleHQgPSBUUlVFKQpyZWdsYXNfcmVjZXRhcyA8LSBhcHJpb3JpKHJlY2V0YXMsIHBhcmFtZXRlciA9IHBhcnMpCmBgYAoKYGBge3J9CmFncmVnYXJfaHlwZXJsaWZ0IDwtIGZ1bmN0aW9uKHJlZ2xhcywgdHJhbnMpewogIHF1YWxpdHkocmVnbGFzKSA8LSBjYmluZChxdWFsaXR5KHJlZ2xhcyksIAoJaHlwZXJfbGlmdCA9IGludGVyZXN0TWVhc3VyZShyZWdsYXMsIG1lYXN1cmUgPSAiaHlwZXJMaWZ0IiwgCgl0cmFuc2FjdGlvbnMgPSB0cmFucykpCiAgcmVnbGFzCn0KcmVnbGFzX3JlY2V0YXMgPC0gYWdyZWdhcl9oeXBlcmxpZnQocmVnbGFzX3JlY2V0YXMsIHJlY2V0YXMpCmBgYAoKCiMjIEFuw6FsaXNpcyBkZSBwYXJlcyBjb211bmVzCgpgYGB7cn0KbGlicmFyeShhcnVsZXNWaXopCnJlZ2xhc18xIDwtIHN1YnNldChyZWdsYXNfcmVjZXRhcywgaHlwZXJfbGlmdCA+IDEuMSAmIHN1cHBvcnQgPiAwLjEgJiBjb25maWRlbmNlID4gMC40MCkKbGVuZ3RoKHJlZ2xhc18xKQpyZWdsYXNfdGFtXzIgPC0gc3Vic2V0KHJlZ2xhc18xLCBzaXplKHJlZ2xhc18xKT09MikKI2luc3BlY3QocmVnbGFzX3RhbV8yICU+JSBzb3J0KGJ5ID0gJ2h5cGVyX2xpZnQnKSkgCnBsb3QocmVnbGFzXzEgJT4lIHN1YnNldChzdXBwb3J0ID4gMC4yKSwgZW5naW5lID0gInBsb3RseSIpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04fQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmZyZWNzIDwtIApkZl9yZWdsYXMgPC0gcmVnbGFzX3RhbV8yICU+JSBEQVRBRlJBTUUgJT4lIHJlbmFtZShmcm9tPUxIUywgdG89UkhTKSAlPiUgZGF0YS5mcmFtZQpkZl9yZWdsYXMkd2VpZ2h0IDwtIGxvZyhkZl9yZWdsYXMkbGlmdCkKZ3JhcGhfMSA8LSBhc190YmxfZ3JhcGgoZGZfcmVnbGFzKSAlPiUKICBtdXRhdGUoY2VudHJhbGl0eSA9IGNlbnRyYWxpdHlfZGVncmVlKG1vZGUgPSAiYWxsIikpIApzZXQuc2VlZCg4ODEpCmdncmFwaChncmFwaF8xLCBsYXlvdXQgPSAnZnInKSArCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGFscGhhPWxpZnQpLCAKICAgICAgICAgICAgICAgICBjb2xvdXIgPSAncmVkJywKICAgICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoNCwgJ21tJykpKSArIAogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IGNlbnRyYWxpdHksIGNvbG91ciA9IGNlbnRyYWxpdHkpKSArIAogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCBzaXplPTQsCiAgICAgICAgICAgICAgICAgY29sb3VyID0gJ2dyYXkyMCcsIHJlcGVsPVRSVUUpICsKICB0aGVtZV9ncmFwaChiYXNlX2ZhbWlseSA9ICJzYW5zIikKYGBgCgoKYGBge3J9CnJlZ2xhc18xIDwtIHN1YnNldChyZWdsYXNfcmVjZXRhcywgaHlwZXJfbGlmdCA+IDEuNSAmIGNvbmZpZGVuY2UgPiAwLjEpCmxlbmd0aChyZWdsYXNfMSkKcmVnbGFzX3RhbV8yIDwtIHN1YnNldChyZWdsYXNfMSwgc2l6ZShyZWdsYXNfMSk9PTIpCmxlbmd0aChyZWdsYXNfdGFtXzIpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04fQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmRmX3JlZ2xhcyA8LSByZWdsYXNfdGFtXzIgJT4lIERBVEFGUkFNRSAlPiUgcmVuYW1lKGZyb209TEhTLCB0bz1SSFMpICU+JSBhc19kYXRhX2ZyYW1lCmRmX3JlZ2xhcyR3ZWlnaHQgPC0gbG9nKGRmX3JlZ2xhcyRoeXBlcl9saWZ0KQpncmFwaF8xIDwtIGFzX3RibF9ncmFwaChkZl9yZWdsYXMpICU+JQogIG11dGF0ZShjZW50cmFsaXR5ID0gY2VudHJhbGl0eV9kZWdyZWUobW9kZSA9ICJhbGwiKSkgCgpnZ3JhcGgoZ3JhcGhfMSwgbGF5b3V0ID0gJ2ZyJywgc3RhcnQudGVtcD0xMDApICsKICBnZW9tX2VkZ2VfbGluayhhZXMoYWxwaGE9bGlmdCksIAogICAgICAgICAgICAgICAgIGNvbG91ciA9ICdyZWQnLAogICAgICAgICAgICAgICAgIGFycm93ID0gYXJyb3cobGVuZ3RoID0gdW5pdCg0LCAnbW0nKSkpICsgCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gY2VudHJhbGl0eSwgY29sb3VyID0gY2VudHJhbGl0eSkpICsgCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHNpemU9NCwKICAgICAgICAgICAgICAgICBjb2xvdXIgPSAnZ3JheTIwJywgcmVwZWw9VFJVRSkgKwogIHRoZW1lX2dyYXBoKGJhc2VfZmFtaWx5ID0gInNhbnMiKQpgYGAKCkV4cG9ydGFtb3MgcGFyYSBleGFtaW5hciBlbiBHZXBoaToKCgpgYGB7cn0Kd3JpdGVfY3N2KGRmX3JlZ2xhcyAlPiUgcmVuYW1lKHNvdXJjZT1mcm9tLCB0YXJnZXQ9dG8pICU+JQogICAgICAgICAgICBzZWxlY3QoLWNvdW50KSwgCiAgICAgICAgICBwYXRoPSdyZWdsYXMuY3N2JykKYGBgCgoKIyMjIE5vdGEKCkxhIGNvbWJpbmFjacOzbiBfY29ybl8geSBfc3RhcmNoXyBwdWVkZSBkZWJlcnNlIGVuIHBhcnRlIGEgdW5hIHNlcGFyYWNpw7NuIGluY29ycmVjdGEgZW4gZWwgCnByb2Nlc2FtaWVudG8gZGUgbG9zIGRhdG9zIChjb3JuIHN0YXJjaCBvIG1haXplbmEgY29udmVydGlkbyBlbiBkb3MgaW5ncmVkaWVudGVzLCBjb3JuIHkgc3RhcmNoKToKCmBgYHtyfQpkZl9yZWdsYXMgJT4lIGZpbHRlcihmcm9tID09ICJ7Y29ybn0iLCB0byA9PSAie3N0YXJjaH0iKQpgYGAKCkxhIGNvbmZpYW56YSBlcyBjb25zaWRlcmFibGVtZW50ZSBhbHRhLCBhdW5xdWUgdGVuZW1vcyBwb2NvcyBkYXRvcyBkZSBlc3RhIGNvbWJpbmFjacOzbi4gUG9kZW1vcyBleGFtaW5hciBhbGd1bm9zIGVqZW1wbG9zOgoKYGBge3J9CnJlY2V0YXMgJT4lIGtlZXAofiAidG9tYXRvIiAlaW4lIC54ICYgImNvcm4iICVpbiUgLngpICU+JSBoZWFkKDEwKQpgYGAKCgo=